{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 实战回归",
   "id": "6cffbee049095838"
  },
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.830166Z",
     "start_time": "2025-03-06T12:33:59.828105Z"
    }
   },
   "source": [
    "import sys\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import sklearn\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from tqdm.auto import tqdm\n",
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt"
   ],
   "outputs": [],
   "execution_count": 45
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "显示系统硬件信息",
   "id": "e5a5eca907ca3973"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.864675Z",
     "start_time": "2025-03-06T12:33:59.862181Z"
    }
   },
   "cell_type": "code",
   "source": [
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)"
   ],
   "id": "a8d99b32381564c3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.9.1\n",
      "numpy 2.0.0\n",
      "pandas 2.2.2\n",
      "sklearn 1.6.1\n",
      "torch 2.6.0+cu126\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 46
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 准备数据",
   "id": "7cc90f1d47eedf82"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.883933Z",
     "start_time": "2025-03-06T12:33:59.875682Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='.')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "451957e12903949d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 47
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据预处理",
   "id": "808062654903a3e3"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.890071Z",
     "start_time": "2025-03-06T12:33:59.884937Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train], #训练集\n",
    "    \"valid\": [x_valid, y_valid], #验证集\n",
    "    \"test\": [x_test, y_test], #测试集\n",
    "} #把3个数据集都放到字典中\n"
   ],
   "id": "4c9448446d49563b",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 48
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.895132Z",
     "start_time": "2025-03-06T12:33:59.890071Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "\n",
    "\n",
    "scaler = StandardScaler() #标准化\n",
    "scaler.fit(x_train) #fit和fit_transform的区别，fit是计算均值和方差，fit_transform是先fit，然后transform\n",
    "np.array(1).shape"
   ],
   "id": "6a98de652000081b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 49
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "构建数据集",
   "id": "7a96d6f68d9fa503"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.903374Z",
     "start_time": "2025-03-06T12:33:59.899137Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式，把数据分为训练集、验证集、测试集，然后用Dataset类封装成私有数据集\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode] #x,y都是ndarray类型\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float() #from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1) #处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "            \n",
    "    def __len__(self): #必须写\n",
    "        return len(self.x) #返回数据集的长度,0维的size\n",
    "    \n",
    "    def __getitem__(self, idx): #idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "4cac9f5658d0196b",
   "outputs": [],
   "execution_count": 50
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "转换为DataLoader",
   "id": "46764aefbea36764"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.909846Z",
     "start_time": "2025-03-06T12:33:59.903374Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "#放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "batch_size = 64 #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "9d91694c5b3cdb1",
   "outputs": [],
   "execution_count": 51
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 模型构建",
   "id": "72857bd9abe80eef"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.915765Z",
     "start_time": "2025-03-06T12:33:59.911850Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 因为是回归模型：所有只有一个输出，所以输出层只有一个神经元\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "    \n",
    "    # 前向传播\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "id": "94c9b802bec1805",
   "outputs": [],
   "execution_count": 52
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "EarlyStopping",
   "id": "b08f5efc2d8891ce"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.921774Z",
     "start_time": "2025-03-06T12:33:59.915765Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "c6d387f9d2c6f25b",
   "outputs": [],
   "execution_count": 53
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "评估函数loss_func",
   "id": "94d94aa0e461080c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:33:59.930593Z",
     "start_time": "2025-03-06T12:33:59.927779Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)"
   ],
   "id": "536f8d8f3b83cbb7",
   "outputs": [],
   "execution_count": 54
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "训练函数",
   "id": "1601b2db25199272"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:34:31.635147Z",
     "start_time": "2025-03-06T12:33:59.949596Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:#11610/16=726\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas) #得到预测值\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item() #转为cpu类型，item()是取值\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss) #根据验证集的损失来实现早停\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "id": "9a35414eda003532",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/18200 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "23113bc8bb5e4e6886474b23733f83d7"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 55
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 画图观察损失变化和模型效果",
   "id": "bff8a7e3e88601cc"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:34:31.694989Z",
     "start_time": "2025-03-06T12:34:31.635365Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "id": "c19941cb991cd12d",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 56
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 测试集",
   "id": "a4260a6d9be0151d"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-06T12:34:31.740966Z",
     "start_time": "2025-03-06T12:34:31.694989Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, test_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "68499a0e3602d723",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3428\n"
     ]
    }
   ],
   "execution_count": 57
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
